package io.libp2p.example.chat

import android.net.Uri
import android.net.wifi.WifiManager
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.text.TextUtils
import android.util.Base64
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.Glide
import com.google.gson.Gson
import com.hjq.permissions.OnPermissionCallback
import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions
import com.luck.picture.lib.basic.PictureSelector
import com.luck.picture.lib.config.SelectMimeType
import com.luck.picture.lib.config.SelectModeConfig
import com.luck.picture.lib.entity.LocalMedia
import com.luck.picture.lib.interfaces.OnResultCallbackListener
import io.libp2p.example.chat.DeviceAdapter.DeviceInfo
import io.libp2p.example.chat.databinding.ActivityMainBinding
import io.libp2p.example.localserver.FileServer
import org.webrtc.PeerConnection
import java.net.URI
import java.util.UUID
import java.util.concurrent.CompletableFuture.runAsync
import kotlin.concurrent.thread


class MainActivity : AppCompatActivity() {
    private lateinit var multicastLock: WifiManager.MulticastLock

    var fileRecv:FileRecv? = null

    var lastFileId:String =""

    val TAG:String ="MainActivity"

    //private lateinit var chatNode: ChatNode
    private lateinit var binding: ActivityMainBinding

    private val mainHandler = Handler(Looper.getMainLooper())

    //private var spdSocketClient: SpdSocketClient = SpdSocketClient(URI.create("ws://10.0.2.202:16162"))
    //private var spdSocketClient: SpdSocketClient = SpdSocketClient(URI.create("wss://ue5-demo.voidtech.com.cn:7043"))
    private var spdSocketClient: SpdSocketClient = SpdSocketClient(URI.create("ws://115.190.89.224:27575"))
    //var spdSocketClient = OkhttpWebSocket("wss://ue5-demo.voidtech.com.cn:7043")
    //var spdSocketClient = OkhttpWebSocket("ws://10.0.2.221:8081")

    var peerConnectHelperMap = mutableMapOf<String, PeerConnectHelper>()

    private val gson = Gson()

    lateinit var natsHelper:NatsHelper

    val deviceAdapter = DeviceAdapter()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.send.setOnClickListener { sendText() }
        binding.SendVideo.setOnClickListener { selectVideo() }
        binding.sendImage.setOnClickListener { selectImage() }
        binding.confirmButton.setOnClickListener { closeRecvFile() }
        binding.startConnect.setOnClickListener { startConnect() }

        binding.deviceRecycle.layoutManager = LinearLayoutManager(this)
        binding.deviceRecycle.adapter = deviceAdapter

        deviceAdapter.setOnItemClickListener { _, _, position ->
            val deviceInfo = deviceAdapter.data[position]
            OpenUtils.openBrowser(this@MainActivity, deviceInfo.address)
        }

        StunText.readText(this)

        startMatsConnect()

        FileServer.execute(this)

        requestPermission()
    }

    private fun startMatsConnect(){
        natsHelper = NatsHelper(connectCallback)

        natsHelper.startConnect()
    }

    override fun onDestroy() {
        super.onDestroy()

        releaseMulticastLock()
    } // onDestroy

    private val connectCallback = object : ConnectCallback{
        override fun onOpen() {
            runAsync {
                acquireMulticastLock()

                chatMessage("Nats connect success.")

                chatMessage("This node listening on ${SpdJson.deviceId}")

                startConnect()
            }
        }

        override fun onClose() {
            chatMessage("Nats connect fail!")

            mainHandler.postDelayed({
                natsHelper.reconnect()
            }, 10 * 1000)
        }

        override fun onMessage(msg: SpdJson) {
            receiveMessage(msg)
        }
    }

    private fun receiveMessage(spdJson : SpdJson){
        if (spdJson != null && !TextUtils.isEmpty(spdJson.peerId) && !TextUtils.equals(spdJson.peerId, SpdJson.deviceId)) {

            if(TextUtils.equals(spdJson.type, SpdJson.TYPE_START)){
                var helper = peerConnectHelperMap[spdJson.peerId]

                if(helper == null || helper.isClosed){
                    helper?.release()
                    helper = PeerConnectHelper(this, spdJson.peerId, natsHelper, peerConnectCallback)
                    peerConnectHelperMap[spdJson.peerId] = helper
                    helper.startConnect()
                    MyLog.I(TAG,"PeerConnectHelper 创建新的通道Offer")
                }else{
                    MyLog.E(TAG,"PeerConnectHelper 已经创建Offer")
                }

            }else if (TextUtils.equals(spdJson.destId, SpdJson.deviceId)) {

                if(TextUtils.equals(spdJson.type, SpdJson.TYPE_OFFER)) {
                    var helper = peerConnectHelperMap[spdJson.peerId]

                    if (helper == null || helper.isClosed) {
                        helper?.release()
                        helper = PeerConnectHelper(this, spdJson.peerId, natsHelper, peerConnectCallback)
                        peerConnectHelperMap[spdJson.peerId] = helper
                        helper.startSetRemote(spdJson.sdp)
                        MyLog.W(TAG, "PeerConnectHelper 创建新的通道Answer")
                    } else {
                        MyLog.E(TAG, "PeerConnectHelper 已经创建Answer")
                    }

                }else{
                    peerConnectHelperMap[spdJson.peerId]?.let { helper ->
                        if (TextUtils.equals(spdJson.type, SpdJson.TYPE_CANDI)) {
                            helper.addIce(spdJson)
                        }else if(TextUtils.equals(spdJson.type, SpdJson.TYPE_ANSWER)) {
                            helper.setAnswer(spdJson.sdp)
                        }else if(TextUtils.equals(spdJson.type, SpdJson.TYPE_REMOVE_ICE)) {
                            helper.removeIce(spdJson)
                        }else if(TextUtils.equals(spdJson.type, SpdJson.TYPE_START_P2P)) {
                            helper.startP2PConnect()
                        }
                    }
                }
            }
        }
    }

    private val peerConnectCallback = object : PeerConnectHelper.DataCallback{
        override fun onDataRecv(peerId: String, data: String) {
            chatMessage(data, peerId)
        }

        override fun onDisConnect(peerConnectHelper: PeerConnectHelper, isCreateOffer:Boolean) {
            runOnUiThread {
                MyLog.E(TAG, "PeerConnectHelper onDisConnect isCreateOffer=$isCreateOffer, close connect.")

                peerConnectHelperMap.remove(peerConnectHelper.peerId)?.release()

                if(isCreateOffer) {
                    startConnect()
                }
            }

        }

        override fun onConnectChange(peerConnectHelper: PeerConnectHelper?) {
            runOnUiThread { refreshDeviceList() }
        }
    }

    private fun refreshDeviceList(){
        val deviceList = mutableListOf<DeviceInfo>()

        for(peer in peerConnectHelperMap){
            peer.value.let {
                if(it.isConnect){
                    deviceList.add(DeviceInfo(it.peerId, it.address))
                }
            }
        }

        deviceAdapter.setNewData(deviceList)
    }

    private fun sendText() {
        val msg = binding.line.text.toString().trim()
        if (msg.isEmpty())
            return

        // send message here
        sendDataChannel(msg)

        chatMessage("You > " + msg)

        binding.line.text.clear()
    } // sendText

    private fun chatMessage(msg: String, peerId:String = "") {
        //LibP2pLog.D("chatMessage=$msg")

        if(msg.contains("socket_data")){
            recvSocketData(msg, peerId)
        }else if(msg.contains("file_data")){
            recvFile(msg)
        }else {
            runOnUiThread {
                if(peerId.isNotEmpty()) {
                    binding.chat.append("$peerId > $msg")
                }else{
                    binding.chat.append("$msg")
                }
                binding.chat.append("\n")
                binding.chatScroll.post { binding.chatScroll.fullScroll(View.FOCUS_DOWN) }
            }
        }
    } // chatMessage

    private fun acquireMulticastLock() {
        val wifi = getSystemService(WIFI_SERVICE) as WifiManager
        multicastLock = wifi.createMulticastLock("libp2p-chatter")
        multicastLock.acquire()
    }

    private fun releaseMulticastLock() {
       multicastLock.release()
    }

    private fun selectVideo(){
        PictureSelector.create(this)
            .openGallery(SelectMimeType.ofVideo())
            .setSelectionMode(SelectModeConfig.SINGLE)
            .setImageEngine(GlideEngine.createGlideEngine())
            .forResult(object : OnResultCallbackListener<LocalMedia?> {
                override fun onResult(result: ArrayList<LocalMedia?>) {
                    if(result.isNotEmpty()){
                        result[0]?.let {
                            startSendFile(it.path, it.size, it.fileName)
                        }
                    }
                }

                override fun onCancel() {
                }
            })
    }

    private fun selectImage(){
        PictureSelector.create(this)
            .openGallery(SelectMimeType.ofImage())
            .setImageEngine(GlideEngine.createGlideEngine())
            .setSelectionMode(SelectModeConfig.SINGLE)
            .forResult(object : OnResultCallbackListener<LocalMedia?> {
                override fun onResult(result: ArrayList<LocalMedia?>) {
                    if(result.isNotEmpty()){
                        result[0]?.let {
                            startSendFile(it.path, it.size, it.fileName)
                        }
                    }
                }

                override fun onCancel() {
                }
            })
    }

    private fun startSendFile(path:String, size:Long, name: String){
        peerConnectHelperMap.values
            .filter { it.connectionStatus == PeerConnection.PeerConnectionState.CONNECTED }
            .map { startSendFile(it, path, size, name) }
            .ifEmpty { Toast.makeText(applicationContext, "没有连接的设备", Toast.LENGTH_SHORT).show() }

    }

    private fun startSendFile(pc: PeerConnectHelper, path:String, size:Long, name: String){
        MyLog.D("selectImage onResult, path=$path")

        thread {
            MyLog.D("start send file")
            val start = System.currentTimeMillis()

            val fileId = UUID.randomUUID().toString()
            lastFileId = fileId

            val dataCmd = DataCmd(fileId, DataCmd.CMD_FILE_START)
            dataCmd.fileName = name
            dataCmd.fileSize = size
            sendFileData(pc, dataCmd)

            val bis = contentResolver.openInputStream(Uri.parse(path))

            val fileRecv = FileRecv(fileId, name, size, path)
            showFileSend(fileRecv)

            val buffer = ByteArray(16*1024) // 缓冲区大小
            var bytesRead: Int

            Thread.sleep(100)

            while (bis!!.read(buffer).also { bytesRead = it } != -1) {
                val base64 = Base64.encodeToString(buffer, 0, bytesRead, Base64.DEFAULT)
                sendFileData(pc, DataCmd.parseFileData(fileId, base64))

                fileRecv.addData(base64)
                showFileSend(fileRecv)
            }

            bis!!.close()

            sendFileData(pc, DataCmd(fileId, DataCmd.CMD_FILE_END))

            fileRecv.endRecv()
            showFileSend(fileRecv)

            MyLog.D("send file end, spend:"+(System.currentTimeMillis() - start)+"ms")
        }
    }

    private fun recvSocketData(msg: String, peerId: String){
        val helper = peerConnectHelperMap[peerId]
        val dataCmd = gson.fromJson(msg, DataCmd::class.java)

        dataCmd?.let { helper?.let {
            when (dataCmd.cmd) {
                DataCmd.CMD_SOCKET_CLIENT -> helper.writeDateToClient(dataCmd)

                DataCmd.CMD_SOCKET_SERVER -> helper.writeDateToServer(dataCmd)

                DataCmd.CMD_SOCKET_CLOSE -> helper.closeConnect(dataCmd)

                DataCmd.CMD_CLIENT_CLOSE -> helper.closeClient(dataCmd)

                else -> {}
            }
        }}

    }

    private fun recvFile(msg: String){
        try {
            val dataCmd = Gson().fromJson(msg, DataCmd::class.java)

            dataCmd?.let {
                if(lastFileId.isEmpty() || dataCmd.fileId == lastFileId || (fileRecv!= null && fileRecv!!.isComplete)) {
                    when (it.cmd) {
                        DataCmd.CMD_FILE_START -> {
                            lastFileId = it.fileId
                            fileRecv = FileRecv(applicationContext, it.fileId, it.fileName, it.fileSize)
                        }

                        DataCmd.CMD_FILE_DATA -> {
                            fileRecv?.apply { addData(it.zbase64) }
                        }

                        DataCmd.CMD_FILE_END -> {
                            fileRecv?.apply { endRecv() }
                        }

                        else -> {}
                    }

                    fileRecv?.let { recv -> showFileRecv(recv) }
                }
            }
        }catch (e: Exception){
            e.printStackTrace()
        }

    }

    private fun closeRecvFile(){
        lastFileId = ""
        binding.fileRecv.visibility = View.GONE
        fileRecv?.clear()

    }

    private fun showFileRecv(fileRecv: FileRecv) {
        runOnUiThread {
            if(fileRecv.progress == 0) binding.fileRecv.visibility = View.VISIBLE

            binding.fileStatus.text =  "Status：Receive"
            binding.fileName.text = "FileName：${fileRecv.fileName}"
            binding.fileSize.text = "FileSize：${fileRecv.fileSizeString}"
            binding.fileSpeed.text = "Speed：${fileRecv.speedAvgString}"
            binding.fileProgress.text = "Progress：${fileRecv.progress}%"

            binding.imageView.visibility = if(fileRecv.isComplete) View.VISIBLE else View.INVISIBLE

            if (fileRecv.isComplete){
                Glide.with(binding.root).load(fileRecv.path).into(binding.imageView)
            }
        }
    }

    private fun showFileSend(fileRecv: FileRecv) {
        runOnUiThread {
            if(fileRecv.progress == 0) binding.fileRecv.visibility = View.VISIBLE

            binding.fileStatus.text = "Status：Send"
            binding.fileName.text = "FileName：${fileRecv.fileName}"
            binding.fileSize.text = "FileSize：${fileRecv.fileSizeString}"
            binding.fileSpeed.text = "Speed：${fileRecv.speedAvgString}"
            binding.fileProgress.text = "Progress：${fileRecv.progress}%"

            if(fileRecv.progress == 0) {
                Glide.with(binding.root).load(Uri.parse(fileRecv.path)).into(binding.imageView)
            }
        }
    }

    private fun startConnect(){
        natsHelper.sendSpdJson(SpdJson(SpdJson.TYPE_START, "")){ scu ->
            if(!scu){
                startConnect()
            }
        }
    }

    private fun sendDataChannel(data: String) {
        peerConnectHelperMap.values.forEach{
            if (it.connectionStatus == PeerConnection.PeerConnectionState.CONNECTED) {
                it.sendDataChannel(data)
            }
        }
    }

    private fun sendFileData(peerConnectHelper: PeerConnectHelper, dataCmd: DataCmd) {
        val gson = Gson().toJson(dataCmd)
        if (peerConnectHelper.connectionStatus == PeerConnection.PeerConnectionState.CONNECTED) {
            peerConnectHelper.sendDataChannel(gson)
        }
    }

    private fun requestPermission() {
        XXPermissions.with(this).permission(Permission.READ_EXTERNAL_STORAGE).request(object : OnPermissionCallback {

            override fun onDenied(permissions: MutableList<String>, doNotAskAgain: Boolean) {
                Toast.makeText(this@MainActivity, "文件服务开启失败：权限被拒绝", Toast.LENGTH_SHORT).show()
            }

            override fun onGranted(permissions: MutableList<String>, allGranted: Boolean) {
                thread{ OpenUtils.loadRecentMedia(this@MainActivity) }
            }
        })
    }
}
